package com.guang.page.intercepters;

import com.guang.page.bean.Page;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.reflection.SystemMetaObject;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.Map;

/**
 * Statement prepare(Connection connection, Integer transactionTimeout)
 *
 * @author liguang
 * @date 2022/6/1 11:41
 */
@Intercepts({@Signature(
        type = StatementHandler.class,
        method = "prepare",
        args = {Connection.class, Integer.class})})
public class PageIntercepter implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
        BoundSql boundSql = statementHandler.getBoundSql();
        Object parameterObject = boundSql.getParameterObject();
        Page page = null;
        if (parameterObject != null) {
            if (parameterObject instanceof Page) {
                page = (Page) parameterObject;
            } else if (parameterObject instanceof Map) {
                page = (Page) ((Map<?, ?>) parameterObject).values().stream().filter(value -> value instanceof Page).findFirst().orElse(null);
            }
        }
        if (page != null) {
            String sql = boundSql.getSql();
            String newSql = String.format("select count(*) from (%s) as tmp_page", sql);
            Connection connection = (Connection) invocation.getArgs()[0];
            PreparedStatement preparedStatement = connection.prepareStatement(newSql);
            statementHandler.getParameterHandler().setParameters(preparedStatement);
            ResultSet resultSet = preparedStatement.executeQuery();
            int count = -1;
            if (resultSet != null) {
                // 一定要添加这个，不然在查询出来的时候会有错误：before start of result set
                while (resultSet.next()) {
                    page.setTotal(resultSet.getLong(1));
                    break;
                }
            }
            resultSet.close();
            preparedStatement.close();
            String newExecutorSql = String.format("%s  limit %s  offset %s", boundSql.getSql(), page.getSize(), page.getOffset());
            // 修改boundSql中的值
            SystemMetaObject.forObject(boundSql).setValue("sql", newExecutorSql);
            // 然后来执行新的SQL，将结果集封装到Page中来
            Object proceedResult = invocation.proceed();
            return proceedResult;
        }
        return invocation.proceed();
    }
}
// 如果自己手动设置一下thread中携带自身的数据，如何来进行定义呢？
// 首先想到的是List结构来进行操作，但是list结构，每次想要获取得到想要的结果的时候，都需要遍历，所以不建议使用。
// 那么考虑使用到map，map中的key不是重复的，那么就可以用这个来作为唯一性标识进行使用
// 对于这个key来说，每个线程是公用的，但是value确实是每个线程所独有的！这里的value通常来说是每个线程中保存的数据信息
// 对于线程来说，使用完成之后，需要将其进行过滤掉，否则的话，会造成内存的泄漏！
// 如果说线程只运行一次，那么没有关系，最终会将其GC掉，但是如果说线程是运行多次，那么就需要将这里的map给清除掉
